package calao.storage.bdb;

import static calao.storage.DirectAccess.get;

import calao.storage.Field;
import calao.storage.Index;
import calao.storage.Record;
import calao.storage.Table;

import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;

public class RecordWriter 
{
	
	final Table table;
	
	DatabaseEntry pkeyEntry;
	DatabaseEntry dataEntry;

	RecordWriter(Table table)
	{
		this.table = table;	
	}
	
	void writePrimaryKey(Record record)
	{
		Index primaryIndex = table.getPrimaryKeyIndex();
		TupleOutput key = new TupleOutput();
		for (Field field : primaryIndex) {
			writeValue(field, get(record, field), key);
		}		
		pkeyEntry = Util.toEntry(key);
	}
	
	void writeData(Record record)
	throws DatabaseException 
	{
		Index primaryIndex = table.getPrimaryKeyIndex();
		TupleOutput data = new TupleOutput();
		data.writePackedInt(0); // encoding version
		for (Field<?> field : table.getFieldList()) {
			if (primaryIndex.refersTo(field)) continue;
			// format: field-id number-of-values (value-block)+ 
			data.writePackedInt(BdbStorage.envRegistry.getId(field));
			if (field.isMultiValued()) {
				Object[] values = record.getAll(field);
				if (values!=null) {
					data.writePackedInt(values.length);
					for (Object value : values) {
						writeValueBlock(field, value, data);
					}
				}
				else data.writePackedInt(0);
			}
			else {
				Object value = get(record, field);
				if (value!=null) {
					data.writePackedInt(1);
					writeValueBlock(field, value, data);
				}
				else data.writePackedInt(0);
			}
		}
		dataEntry = Util.toEntry(data);		
	}
	
	void write(Record record)
	throws DatabaseException 
	{
		writePrimaryKey(record);
		writeData(record);
	}
	
	static void writeValue(Field field, Object value, TupleOutput out)
	{
		switch (field.getType().toEnum()) {
		case INTEGER:
			out.writeInt((Integer)value);
			break;
		case SHORT:
			out.writeShort((Short)value);
			break;
		case LONG:
			out.writeLong((Long)value);
			break;
		case DOUBLE:
			out.writeDouble((Double)value);
			break;
		case STRING:
			out.writeString((String)value);
			break;
		case OBJECT:
			TupleDataWriter writer = new TupleDataWriter(out);
			writer.write(value);
			break;
		default:
			throw new UnsupportedOperationException(
					"Field type not supported: "+field.getType().toEnum().name());
		}
	}
	
	static void writeValueBlock(Field field, Object value, TupleOutput out) 
	{
		// format: value-length value
		switch (field.getType().toEnum()) {
		case INTEGER:
			out.writeUnsignedByte(0x84);
			writeValue(field, value, out);
			break;
		case LONG:
		case DOUBLE:
			out.writeUnsignedByte(0x88);
			writeValue(field, value, out);
			break;
		default: {
			int blockStart = out.getBufferLength();
			out.makeSpace(4);
			out.addSize(4);
			int valueStart = out.getBufferLength();
			writeValue(field, value, out);
			int valueLength = out.getBufferLength()-valueStart;
			Util.writeInt(valueLength, out.getBufferBytes(), blockStart);
		}
		}
	}

}
